using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.Practices.Unity;

namespace MiniStock.Infrastructure
{
    public abstract class WorkerBase: MarshalByRefObject
    {
        public string Name { get; set; }

        public event Action<WorkerBase> Starting;
        public event Action<WorkerBase> Started;
        public event Action<WorkerBase> Stopping;
        public event Action<WorkerBase> Stopped;

        [Dependency()]
        public IUnityContainer RootContainer { get; set; }

        static int _counter;
        protected ManualResetEvent StopEvent = new ManualResetEvent(false);
        protected ManualResetEvent StartedEvent = new ManualResetEvent(false);
        protected Thread WorkerThread;
        protected int ControlTimeoutMs = -1;
        protected int PollTimeoutMs = 1000;

        protected WorkerBase()
        {
            _counter++;
        }

        public void SignalStart()
        {
            if(Name == null)
                throw new ApplicationException("Worker name cannot be null.");

            Trace.TraceInformation("Signal start for " + Name);
            if (WorkerThread == null)
            {
                Trace.TraceInformation("Creating worker thread for " + Name);
                WorkerThread = new Thread(Work) { Name = this.Name, };
                StartedEvent.Reset();
                WorkerThread.Start();
            }
        }

        public void Start()
        {
            SignalStart();
            if(!StartedEvent.WaitOne(ControlTimeoutMs))
            {
                WorkerThread.Abort();
                throw new ApplicationException("Worker thread could not start in given time, execution aborted");
            }
            Trace.TraceInformation("Started " + Name);

            OnStarted();
        }

        protected virtual void OnStarting()
        {
            if (this.Starting != null)
                this.Starting(this);
        }

        protected virtual void OnStarted()
        {
            if (this.Started != null)
                this.Started(this);
        }

        public void SignalStop()
        {
            Trace.TraceInformation("Signal stop at " + Name);
            StopEvent.Set();
        }

        public void Stop()
        {
            OnStopping();

            SignalStop();
            if (!WorkerThread.Join(ControlTimeoutMs))
            {
                Trace.TraceWarning("Aborting " + Name);
                WorkerThread.Abort();
            }
            Trace.TraceInformation("Stopped " + Name);

            OnStopped();
        }

        protected virtual void OnStopping()
        {
            if (this.Stopping != null)
                this.Stopping(this);
        }

        protected virtual void OnStopped()
        {
            if (this.Stopped != null)
                this.Stopped(this);
        }

        public void Work()
        {
            InitWork();
            StartedEvent.Set();
            while (true)
            {
                try
                {
                    var timeout = PollTimeoutMs;
                    if (DoWork()) timeout = 0;
                    if (StopEvent.WaitOne(timeout))
                        break;
                } catch(Exception ex)
                {
                    Trace.TraceError("Exception from worker: {0}: {1}", Name, ex);
                }
            }

            CleanupWork();
        }

        public abstract void InitWork();

        public abstract bool DoWork();

        public abstract void CleanupWork();
    }
}